package com.nuanshui.framework.utils;


import java.beans.PropertyDescriptor;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

import com.nuanshui.framework.lang.SysStringUtils;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.util.Map;

/**
 * 反射工具类
 * 
 * @author 陈泽权
 * 
 *         下午2:03:54
 */
public class ReflectionUtils {

	public static Object invokeGetterMethod(final Object obj,
			final String propertyName) {
		final String getterMethodName = (new StringBuilder()).append("get")
				.append(SysStringUtils.capitalize(propertyName)).toString();
		return invokeMethod(obj, getterMethodName, new Class[0], new Object[0]);
	}

	public static void invokeSetterMethod(final Object obj,
			final String propertyName, final Object value) {
		invokeSetterMethod(obj, propertyName, value, null);
	}

	public static void invokeSetterMethod(final Object obj,
			final String propertyName, final Object value,
			final Class propertyType) {
		final Class type = propertyType == null ? value.getClass()
				: propertyType;
		final String setterMethodName = (new StringBuilder()).append("set")
				.append(SysStringUtils.capitalize(propertyName)).toString();
		invokeMethod(obj, setterMethodName, new Class[] { type },
				new Object[] { value });
	}

	public static Object getFieldValue(final Object obj, final String fieldName) {
		final Field field = getAccessibleField(obj, fieldName);
		if (field == null)
			throw new IllegalArgumentException((new StringBuilder())
					.append("Could not find field [").append(fieldName)
					.append("] on target [").append(obj).append("]").toString());
		Object result = null;
		try {
			result = field.get(obj);
		} catch (final IllegalAccessException e) {
			e.printStackTrace();
		}
		return result;
	}

	public static void setFieldValue(final Object obj, final String fieldName,
			final Object value) {
		final Field field = getAccessibleField(obj, fieldName);
		if (field == null)
			throw new IllegalArgumentException((new StringBuilder())
					.append("Could not find field [").append(fieldName)
					.append("] on target [").append(obj).append("]").toString());
		try {
			field.set(obj, value);
		} catch (final IllegalAccessException e) {
			e.printStackTrace();
		}
	}

	public static Field getAccessibleField(final Object obj,
			final String fieldName) {
		Assert.notNull(obj, "object\u4E0D\u80FD\u4E3A\u7A7A");
		Assert.hasText(fieldName, "fieldName");
		for (Class superClass = obj.getClass(); !superClass.getSimpleName()
				.equals("Object");)
			try {
				final Field field = superClass.getDeclaredField(fieldName);
				field.setAccessible(true);
				return field;
			} catch (final NoSuchFieldException e) {
				superClass = superClass.getSuperclass();
			}

		return null;
	}

	public static Object invokeMethod(final Object obj,
			final String methodName, final Class parameterTypes[],
			final Object args[]) {
		final Method method = getAccessibleMethod(obj, methodName,
				parameterTypes);
		if (method == null)
			throw new IllegalArgumentException((new StringBuilder())
					.append("Could not find method [").append(methodName)
					.append("] on target [").append(obj).append("]").toString());
		try {
			return method.invoke(obj, args);
		} catch (final Exception e) {
			if (e.getClass().getSimpleName()
					.equals("javax.validation.ConstraintViolationException"))
				e.printStackTrace();
			throw convertReflectionExceptionToUnchecked(e);
		}
	}

	public static Method getAccessibleMethod(final Object obj,
			final String methodName, final Class parameterTypes[]) {
		Assert.notNull(obj, "对象不能为空");
		for (Class superClass = obj.getClass(); !superClass.getSimpleName()
				.equals("Object");)
			try {
				final Method method = superClass.getDeclaredMethod(methodName,
						parameterTypes);
				method.setAccessible(true);
				return method;
			} catch (final NoSuchMethodException e) {
				superClass = superClass.getSuperclass();
			}

		return null;
	}

	public static Class getSuperClassGenricType(final Class clazz) {
		return getSuperClassGenricType(clazz, 0);
	}

	public static Class getSuperClassGenricType(final Class clazz,
			final int index) {
		final Type genType = clazz.getGenericSuperclass();
		try {
			if (!(genType instanceof ParameterizedType)) {
				return Class.forName("java.lang.Object");
			}
			final Type params[] = ((ParameterizedType) genType)
					.getActualTypeArguments();
			if (index >= params.length || index < 0) {
				return Class.forName("java.lang.Object");
			}
			if (!(params[index] instanceof Class)) {
				return Class.forName("java.lang.Object");
			} else {
				return (Class) params[index];
			}
		} catch (final ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static RuntimeException convertReflectionExceptionToUnchecked(
			final Exception e) {
		if ((e instanceof IllegalAccessException)
				|| (e instanceof IllegalArgumentException)
				|| (e instanceof NoSuchMethodException))
			return new IllegalArgumentException("Reflection Exception.", e);
		if (e instanceof InvocationTargetException)
			return new RuntimeException("Reflection Exception.",
					((InvocationTargetException) e).getTargetException());
		if (e instanceof RuntimeException)
			return (RuntimeException) e;
		else
			return new RuntimeException("Unexpected Checked Exception.", e);
	}


	/**
	 * 设置object 属性为null
	 */
	public static void ignoreProperties(Object source, String... ignoreProperties){
		Class<?> actualEditable = source.getClass();
		PropertyDescriptor[] sourcePds = org.springframework.beans.BeanUtils.getPropertyDescriptors(actualEditable);
		List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
		for (PropertyDescriptor sourcePd : sourcePds) {
			Method writeMethod = sourcePd.getWriteMethod();
			if (writeMethod != null && (ignoreList != null && ignoreList.contains(sourcePd.getName()))) {
				setPropertyWithNull(source, sourcePd, writeMethod);
			}
		}
	}

	/**
	 * 保留object属性,其他都为null
	 */
	public static void staggedProperties(Object source, String... staggedProperties){
		Class<?> actualEditable = source.getClass();
		PropertyDescriptor[] sourcePds = org.springframework.beans.BeanUtils.getPropertyDescriptors(actualEditable);
		List<String> ignoreList = (staggedProperties != null ? Arrays.asList(staggedProperties) : null);
		for (PropertyDescriptor sourcePd : sourcePds) {
			Method writeMethod = sourcePd.getWriteMethod();
			if (writeMethod != null && (ignoreList != null && !ignoreList.contains(sourcePd.getName()))) {
				setPropertyWithNull(source, sourcePd, writeMethod);
			}
		}
	}


	private static void setPropertyWithNull(Object source, PropertyDescriptor sourcePd, Method writeMethod) {
		if (sourcePd != null) {
			Method readMethod = sourcePd.getReadMethod();
			if (readMethod != null &&
					ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())&&
					!writeMethod.getParameterTypes()[0].isPrimitive()
					) {
				try {
					if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
						readMethod.setAccessible(true);
					}
					if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
						writeMethod.setAccessible(true);
					}
					writeMethod.invoke(source,new Object[]{null});
				}
				catch (Throwable ex) {
					throw new FatalBeanException(
							"Could not set property '" + sourcePd.getName() , ex);
				}
			}
		}
	}

	/**
	 * 指定需要的properties 生成对应的Map
	 */
	public static Map<String,Object> generateMap(Object source, String... needProperties){
		Map<String,Object> propMap = new HashMap<String, Object>();
		Class<?> actualEditable = source.getClass();
		PropertyDescriptor[] sourcePds = org.springframework.beans.BeanUtils.getPropertyDescriptors(actualEditable);
		List<String> ignoreList = (needProperties != null ? Arrays.asList(needProperties) : null);
		for (PropertyDescriptor sourcePd : sourcePds) {
			Method readMethod = sourcePd.getReadMethod();
			if (readMethod != null && (ignoreList != null && ignoreList.contains(sourcePd.getName()))) {
				try {
					Object value = readMethod.invoke(source,null);
					propMap.put(sourcePd.getName(),value);
				}  catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		return propMap;
	}
}